package token

import (
	"strconv"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log"
	commonTypes "hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract"
	"hundsun.com/hsl/hschain/contract/native/token/types"
	ut "hundsun.com/hsl/hschain/contract/native/token/util"
	ecom "hundsun.com/hsl/hschain/executor/common"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
	pbconfig "hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/protos/execute"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/account"
)

// Reg 注册执行器名称
func Reg(cfg *config.PluginConfig) {
	height := uint64(0)
	if cfg != nil {
		if v, ok := cfg.Parameters["height"]; ok {
			h, err := strconv.ParseInt(v, 10, 64)
			if err == nil && h > 0 {
				height = uint64(h)
			}
		}
	}
	contract.Register(types.NativeToken, newNativeToken, false, true, height)
}

type nativeToken struct{}

func newNativeToken() contract.Contract {
	return &nativeToken{}
}

// Init 初始化
func (na *nativeToken) Init() {}

// Run 运行
func (na *nativeToken) Run(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	// TODO 后面根据payload类型实现交易的不同流程区分

	// 创世区块交易执行
	if gtx.GetTx().GetFrom() == common.GenesisAddress {
		return na.RunGenesis(ctx, gtx)
	}
	// 冻结/解冻交易执行
	if gtx.GetTx().GetTo() == "" && gtx.GetTx().Payload != nil {
		return na.RunFrozen(ctx, gtx)
	}
	// 普通转帐交易执行
	return na.RunNormal(ctx, gtx)
}

func (na *nativeToken) RunGenesis(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	// TODO 后续铸币交易的创建可移到共识模块
	log.Infof("This is coinbase tx")
	tx := gtx.GetTx()
	payload := &pbconfig.GenesisTransactionPayload{}
	err := proto.Unmarshal(tx.Payload, payload)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_UNKNOWN_PAYLOAD
		return gtx, nil
	}
	payloadAllocs := payload.GetAlloc()
	for _, ac := range payloadAllocs {
		addr := ac.Address
		balance := ac.Balance
		newAccount := &pbcommon.Account{
			InternalAddress: addr,
			Balance:         balance,
			Type:            pbcommon.Account_USER}
		kv, _ := account.KV(newAccount)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogGenerate, Data: kv.Value})
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	}
	return gtx, nil
}

func (na *nativeToken) RunNormal(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	tx := gtx.GetTx()
	from := gtx.GetTx().GetFrom()
	to := gtx.GetTx().GetTo()
	acc := account.NewAccountDB(ctx)
	fromAccount, _ := acc.GetAccount(from)
	if fromAccount == nil {
		log.Warnf("from account is not exist, create new addr is %s", from)
		fromAccount = &pbcommon.Account{InternalAddress: from, Type: pbcommon.Account_USER}
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogGenerate, Data: kv.Value})
	}

	fee := tx.GetFee()
	genesisPayload := pb.GetGenesisTransactionPayload()
	if genesisPayload.GetFeeLimit() != nil && genesisPayload.GetFeeLimit().GetMinTxFee() > 0 {
		if fee < genesisPayload.GetFeeLimit().GetMinTxFee() {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FEE_NOT_ENOUGH
			return gtx, nil
		}
	}

	// TODO 后续决定余额不足的情况下是否扣除手续费
	value := tx.GetValue()
	fromBalance := fromAccount.GetBalance()
	if value+fee > fromBalance {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_INSUFFICIENT_FUNDS
		return gtx, nil
	}

	//更新from账户余额
	fromBalance = fromBalance - value - fee
	fromAccount.Balance = fromBalance
	kv, _ := account.KV(fromAccount)
	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
	// 这里payload内部存放当前变更之后的账户信息
	gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogFee, Data: kv.Value})

	if to == string(common.BlackHoleAddress) {
		// TODO 投票交易处理 这么处理的原因？
		log.Debugf("This is vote transaction")
		key := db.GenDBKey([]byte(common.BlackHoleAddress), []byte(common.SpecialWriteSetKeyFromAccountBalance))
		kv := &pbcommon.KeyValue{
			Key:   key,
			Value: []byte(strconv.FormatUint(fromBalance, 10))}
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	} else {
		//普通转账交易
		toAccount, _ := acc.GetAccount(to)
		if toAccount == nil {
			//不存在则创建
			toAccount = &pbcommon.Account{InternalAddress: to,
				Balance: value,
				Type:    pbcommon.Account_USER}
		} else {
			toBalance := toAccount.GetBalance()
			toBalance = toBalance + value
			toAccount.Balance = toBalance
		}
		kv, _ := account.KV(toAccount)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogTransfer, Data: kv.Value})
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	}
	return gtx, nil
}

// runFrozen 运行冻结/解冻功能
// TODO 后续该功能需要根据实际需求设计
func (na *nativeToken) RunFrozen(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	tx := gtx.GetTx()
	from := gtx.GetTx().GetFrom()

	acc := account.NewAccountDB(ctx)
	fromAccount, _ := acc.GetAccount(from)
	if fromAccount == nil {
		log.Warnf("from account is not exist, create new addr is %s", from)
		fromAccount = &pbcommon.Account{InternalAddress: from, Type: pbcommon.Account_USER}
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogGenerate, Data: kv.Value})
	}

	fee := tx.GetFee()
	genesisPayload := pb.GetGenesisTransactionPayload()
	if genesisPayload.GetFeeLimit() != nil && genesisPayload.GetFeeLimit().GetMinTxFee() > 0 {
		if fee < genesisPayload.GetFeeLimit().GetMinTxFee() {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FEE_NOT_ENOUGH
			return gtx, nil
		}
	}

	balOpt := &ledger.BalanceFreezeUnfreezeOperation{}
	err := proto.Unmarshal(tx.GetPayload(), balOpt)
	if err != nil {
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_UNKNOWN_PAYLOAD
		return gtx, nil
	}

	freezeHandle := ut.NewFreezeRecord(ctx)
	blockNumber := ctx.GetHeight()
	txIndex := gtx.GetTxIndex()
	var optRecord *ledger.BalanceFreezeUnfreezeRecord
	if freezeAccount(balOpt) {
		log.Debugf("tx [%d:%d] is freeze account balance", blockNumber, txIndex)
		// check
		value := balOpt.GetAmount()
		fee := tx.GetFee()
		if value+fee > fromAccount.GetBalance() {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_INSUFFICIENT_FUNDS
			return gtx, nil
		}
		fromAccount.Balance -= value + fee
		fromAccount.Frozen += value
		freezeTx := &pbcommon.TxLocation{BlockNumber: blockNumber, TransactionIndex: txIndex}
		optRecord = &ledger.BalanceFreezeUnfreezeRecord{AccountAddress: from, Amount: value, FreezeTx: freezeTx}
	} else if unfreezeAccount(balOpt) {
		log.Debugf("tx [%d:%d] is unfreeze account balance", blockNumber, txIndex)
		targetTx := balOpt.GetTargetTx()
		var record *ledger.BalanceFreezeUnfreezeRecord
		record, err := freezeHandle.GetFreezeRecord(targetTx)
		if err == nil {
			fromAccount.Frozen -= record.Amount
			fromAccount.Balance += record.Amount
			unfreezeTx := &pbcommon.TxLocation{BlockNumber: blockNumber, TransactionIndex: txIndex}
			optRecord = &ledger.BalanceFreezeUnfreezeRecord{AccountAddress: from, FreezeTx: targetTx,
				Amount: record.Amount, UnfreezeTx: unfreezeTx}
		}
	}

	if optRecord != nil {
		// update fromaccount
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		// 这里payload内部存放当前变更之后的账户信息
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogFrozen, Data: kv.Value})
		// update freezeRecord
		recordKV, _ := ut.FreezeRecordKV(optRecord)
		ctx.GetStateHandle().SetLocal(recordKV.Key, recordKV.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, recordKV)
		// 这里payload内部存放当前变更之后的record
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: commonTypes.TyLogFrozen, Data: recordKV.Value})
	}
	return gtx, nil
}

func freezeAccount(opt *ledger.BalanceFreezeUnfreezeOperation) bool {
	if opt != nil && opt.GetContent() != nil &&
		(opt.OperationCode == ledger.BalanceFreezeUnfreezeOperation_TemporaryFreeze ||
			opt.OperationCode == ledger.BalanceFreezeUnfreezeOperation_PermanentFreeze) {
		return true
	}
	return false
}

func unfreezeAccount(opt *ledger.BalanceFreezeUnfreezeOperation) bool {
	if opt != nil && opt.GetContent() != nil &&
		opt.OperationCode == ledger.BalanceFreezeUnfreezeOperation_Unfreeze {
		return true
	}
	return false
}

// Rollback 回滚
func (na *nativeToken) Rollback(ctx *ecom.ExecContent, tx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	return nil, nil
}
